<?php

namespace app\common\util;

use app\common\model\trOperateLog;
use app\common\model\trResource;
use think\facade\Env;

class Tools
{
    /*curl 请求*/
    public static function curlRequest($requestUrl, $requestArgs, $header = ['Content-Type: application/json'], $type = 1, $timeout = 30)
    {
        if($type){ //post请求
            $requestArgs = http_build_query($requestArgs, "", "&");
        }else{
            if($requestArgs && is_string($requestArgs)){
                $requestUrl = $requestUrl . "?" . $requestArgs;
            }else{
                $requestUrl = $requestUrl . "?" . implode("&", $requestArgs);
            }
            $requestArgs = '';
        }
        $output = self::curl($requestUrl, $requestArgs, $header, $timeout);
        $output = json_decode($output, 1) ? json_decode($output, 1) : $output;
        return $output;
    }

    public static function curl($url, $data = "", $header = ['Content-Type: application/json'], $timeout = 200)
    {
        $ch = curl_init($url);
        if(empty($data)){
            curl_setopt($ch, CURLOPT_HTTPGET, 1);
        }else {
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        }
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
        if(!empty($header)) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        }
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        $output = curl_exec($ch);
        $errno = curl_errno($ch);
        if ($errno) {
            $info  = curl_getinfo( $ch );
            $info['errno'] = $errno;
            if(request()->server("IS_SCF")) {
                $targetDir = "car_tp5/curl/error.txt";
            }else{
                $targetDir = Env::get("root_path"). "/public/curl_error.txt";
            }
            Tools::writeLog(date("Y-m-d H:i")."--执行curl出错（错误原因查询:http://curl.haxx.se/libcurl/c/libcurl-errors.html）：".var_export($info,1), $targetDir, false);
            return false;
        }
        curl_close($ch);
        return $output;
    }

    /*生成GUID*/
    public static function createGUID()
    {
        if (function_exists('com_create_guid') === true) {
            return strtolower(str_replace("-", "", trim(com_create_guid(), '{}')));
        }
        return strtolower(
            sprintf(
                '%04X%04X%04X%04X%04X%04X%04X%04X',
                mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479), mt_rand(32768, 49151), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535)
            )
        );
    }

    // 创建目录
    static function mkdir($path) {
        if (!is_dir($path)) {
            self::mkdir(dirname($path));
            mkdir($path);
        }
        return true;
    }

    // 获取省市区数据   地区包地址：https://github.com/modood/Administrative-divisions-of-China
    public static function getPCAData(){
        $data = @json_decode(@file_get_contents(Env::get("extend_path") . "pca-code.json"), true);
        return $data;
    }

    // 根据代码获取省市区名称
    public static function getNameByPCA($province = 0, $city = 0, $area = 0){
        static $pcaData = [];
        static $provinceData = [];
        static $cityData = [];
        static $areaData = [];
        if(empty($pcaData))
            $pcaData = self::getPCAData();
        $name = '';
        if(!empty($province)) {
            if(empty($provinceData[$province]))
                $provinceData = self::array_replace($pcaData, "code");
            $name .= $provinceData[$province]["name"] . " "; //选中的省的数据

            if(!empty($city)) {
                if(empty($cityData[$city])) {
                    $cityData = $provinceData[$province]["children"];
                    $cityData = self::array_replace($cityData, "code");
                }
                $name .= $cityData[$city]["name"] . " "; //选中的市的数据

                if(!empty($area) && isset($cityData[$city]["children"])) {
                    if(empty($areaData[$area])) {
                        $areaData = $cityData[$city]["children"];
                        $areaData = self::array_replace($areaData, "code");
                    }
                    $name .= $areaData[$area]["name"]; //选中的区的数据
                }
            }
        }
        return trim($name);
    }

    /* 数组排序 */
    public static function array_sort($array, $column, $sort = 'SORT_ASC')
    {
        $new_array = array();
        $sort_array = array();
        foreach ($array as $key => $value) {
            if (!is_array($value)) {
                $sort_array[$key] = $value;
            } else {
                if(isset($value[$column])){
                    $sort_array[$key] = $value[$column];
                }
            }
        }
        switch ($sort) {
            case 'SORT_ASC':
                asort($sort_array);
                break;
            case 'SORT_DESC':
                arsort($sort_array);
                break;
        }
        foreach ($sort_array as $key => $value) {
            $new_array[$key] = $array[$key];
        }
        return $new_array;
    }

    public static function array_replace($array, $column)
    {
        if(empty($array))
            return [];
        $new_array = array();
        foreach ($array as $key => $value) {
            if(isset($value[$column])){
                $new_array[$value[$column]] = $value;
            }
        }
        return $new_array;
    }

    // 数据转为form-data格式
    static function getFormData($param, $file = []) {
        $body = array();
        // 普通参数
        foreach ($param as $key => $value) {
            $body_part = "Content-Disposition: form-data; name=\"$key\"\r\n";
            $body_part .= "\r\n$value";
            $body[] = $body_part;
        }

        // 上传文件处理
        foreach ($file as $key => $value) {
            $body_part = "Content-Disposition: form-data; name=\"$key\"; filename=\"{$value['name']}\"\r\n";
            $body_part .= "Content-type: {$value['type']}\r\n";
            $body_part .= "\r\n".file_get_contents($value['tmp_name']);
            $body[] = $body_part;
        }

        if(!empty(request()->header("content-type"))) {
            $boundary = substr(request()->header("content-type"), strpos(request()->header("content-type"), "=") + 1);
        }else{
            $boundary = "----WebKitFormBoundary9o8FyCiN8ImsAfEj";
        }

        // multipart-body
        $multipart_body = "--$boundary\r\n";
        // 拼接各个域
        $multipart_body .= implode("\r\n--$boundary\r\n", $body);
        // 最后一个不同的 boundary
        $multipart_body .= "\r\n--$boundary--";

        return [$multipart_body, $boundary];
    }

    // 保存图片到数据库并返回id和相对地址
    static function saveFileToDatabase($path, $userId = 0){
        if(is_file($path)){
            $file = pathinfo($path);
            $return = [
                'id' => $file["filename"],
                'url' => substr($path, strlen(request()->server("DOCUMENT_ROOT") . __PUBLIC__)),
            ];
            if(empty(trResource::getValue("url", ["id" => $return["id"]]))) {
                trResource::add(array(
                    'id' => $return["id"],
                    'name' => $file['basename'],
                    'location' => explode('/', request()->path())[0],
                    'url' => $return['url'],
                    "user_id" => $userId,
                    'time' => time()
                ));
            }
            return $return;
        }
        return ["id" => "", "url" => ""];
    }

    /* 把字段名转为驼峰格式 */
    public static function transferFields($data){
        if(empty($data)){
            return $data;
        }
        if(is_array($data)){
            $newData = [];
            foreach ($data as $k => $v){
                if(is_array($v)){
                    $v = self::transferFields($v);
                }
                $newData[parse_name($k)] = $v;
            }
            return $newData;
        }else{
            return parse_name($data);
        }
    }

    //通过字段类型返回对应的验证规则
    public static function checkDataBaseFields($tableName, $data){
        if(!config("isOpenBaseDataCheck") || empty($data))
            return true;
        static $fieldsType = [];
        if(empty($fieldsType[$tableName])) {
            $fieldsType[$tableName] = db()->getFieldsType($tableName);
        }
        foreach ($fieldsType[$tableName] as $key => $type){
            if(isset($data[$key]) && !is_object($data[$key])){
                if(($i = strpos($type, "(")) !== false){
                    $type = substr($type, 0, $i);
                    $size = substr($type, $i, strlen($type)-1);
                }
                switch ($type){
                    case "int":case "tinyint":case "smallint":case "integer":case "bigint":
                        if($data[$key] != intval($data[$key])){
                            return ErrorCode::code("params_format", parse_name($key));
                        }
                        break;
                    case "varchar":case "char":
                        if(!empty($size) && mb_strlen($data[$key]) > $size){
                            return ErrorCode::code("customized", parse_name($key) . "字段长度最大为$size,当前字符已超出");
                        }
                        break;
                    case "float":case "double":case "decimal":case "numeric":
                        if(!is_numeric($data[$key])){
                            return ErrorCode::code("params_format", parse_name($key));
                        }
                        if(!empty($size)) {
                            if (strpos($size, ".") !== false){
                                $arr = explode(".", $size);
                                $size = $arr[0] + $arr[1] + 1;
                            }
                            if(strlen($data[$key]) > $size){
                                return ErrorCode::code("customized", parse_name($key) . "字段长度最大为$size,当前值已超出");
                            }
                        }
                        break;
                }
            }
        }
        return true;
    }

    //记录调试日志
    public static function writeLog($text = '', $targetDir = '', $cover = false){
        if(request()->server("IS_SCF")) {
            trOperateLog::saveLog(["operate_id" => 0, "content" => $text, "target" => substr($targetDir, 0, 20)]);
            trOperateLog::delByFilter([["create_time", "<=", strtotime("-6 month")]]); // 删除6个月前的日志
        }else {
            if(empty($targetDir)){
                $targetDir = Env::get("root_path") . "/public/log.txt";
            }
            if(strpos($targetDir, Env::get("root_path")) === false){
                $targetDir = request()->server("DOCUMENT_ROOT") . __PUBLIC__ . "/{$targetDir}";
            }
            if($cover && is_file($targetDir)){
                unlink($targetDir);
            }
            self::mkdir(dirname($targetDir));
            if (is_file($targetDir) && 2097152 <= filesize($targetDir)) { // 自动备份到 根目录的runtime/toolsLog/ 下
                try {
                    self::mkdir(Env::get("root_path") . "/runtime/toolsLog");
                    rename($targetDir, Env::get("root_path") . "/runtime/toolsLog/" . time() . '-' . basename($targetDir));
                } catch (\Exception $e) {}
            }
            $log = fopen($targetDir,"a");     	//记录分割的信息的文本文件
            fwrite($log,date("Y-m-d H:i:s")."Tools记录调试日志：".$text."\r\n");
            fclose ($log);
        }
    }

    /**
     *  获取客户端ip地址
     * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
     */
    public static function get_client_ip($type = 0)
    {
        if (isset($_SERVER)) {
            $ip = self::_getClientIPByServer();
        } else {
            $ip = self::_getClientIPByEnv();
        }
        if ($type == 1) {
            $IPAddressNum = sprintf("%u", ip2long($ip));
            return $IPAddressNum ?: 0;
        }
        return $ip;
    }

    public static function _getClientIPByServer()
    {
        $ip = '0.0.0.0';
        if (isset($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } else if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        } else if (isset($_SERVER['HTTP_X_FORWARDED'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED'];
        } else if (isset($_SERVER['HTTP_FORWARDED_FOR'])) {
            $ip = $_SERVER['HTTP_FORWARDED_FOR'];
        } else if (isset($_SERVER['HTTP_FORWARDED'])) {
            $ip = $_SERVER['HTTP_FORWARDED'];
        } else if (isset($_SERVER['REMOTE_ADDR'])) {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        return $ip;
    }

    public static function _getClientIPByEnv()
    {
        $ip = '0.0.0.0';
        if (getenv('HTTP_CLIENT_IP')) {
            $ip = getenv('HTTP_CLIENT_IP');
        } else if (getenv('HTTP_X_FORWARDED_FOR')) {
            $ip = getenv('HTTP_X_FORWARDED_FOR');
        } else if (getenv('HTTP_X_FORWARDED')) {
            $ip = getenv('HTTP_X_FORWARDED');
        } else if (getenv('HTTP_FORWARDED_FOR')) {
            $ip = getenv('HTTP_FORWARDED_FOR');
        } else if (getenv('HTTP_FORWARDED')) {
            $ip = getenv('HTTP_FORWARDED');
        } else if (getenv('REMOTE_ADDR')) {
            $ip = getenv('REMOTE_ADDR');
        }
        return $ip;
    }

    /* 迭代无限极分类 */
    public static function iterationTree($list, $id = 'id', $pid = 'pid', $root = 0, $child = 'children')
    {
        $data = array();
        foreach ($list as $key => $val) {
            if ($val[$pid] == $root) {
                //获取当前$pid所有子类
                unset($list[$key]);
                if (!empty($list)) {
                    $child = self::iterationTree($list, $id, $pid, $val[$id], $child);
                    if (!empty($child)) {
                        $val['children'] = $child;
                    }
                }
                $data[] = $val;
            }
        }
        return $data;
    }

    /*
     * 获取前台登录用户类型(用户名,邮箱，手机号码)
     *
     * @params $login_account 登录账号
     * @return $account_type
     * */
    static function get_login_account_type($login_account)
    {
        $login_type = 'local';
        if ($login_account && strpos($login_account, '@')) {
            $login_type = 'email';
            return $login_type;
        }
        if (preg_match("/^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/", $login_account)) {
            $login_type = 'mobile';
            return $login_type;
        }
        return $login_type;
    }//end function

    //加密解密
    static function authCode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
        $ckey_length = 4;
        $key = md5($key);
        $keya = md5(substr($key, 0, 16));
        $keyb = md5(substr($key, 16, 16));
        $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';

        $cryptkey = $keya . md5($keya . $keyc);
        $key_length = strlen($cryptkey);

        $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
        $string_length = strlen($string);

        $result = '';
        $box = range(0, 255);

        $rndkey = array();
        for ($i = 0; $i <= 255; $i++) {
            $rndkey[$i] = ord($cryptkey[$i % $key_length]);
        }

        for ($j = $i = 0; $i < 256; $i++) {
            $j = ($j + $box[$i] + $rndkey[$i]) % 256;
            $tmp = $box[$i];
            $box[$i] = $box[$j];
            $box[$j] = $tmp;
        }

        for ($a = $j = $i = 0; $i < $string_length; $i++) {
            $a = ($a + 1) % 256;
            $j = ($j + $box[$a]) % 256;
            $tmp = $box[$a];
            $box[$a] = $box[$j];
            $box[$j] = $tmp;
            $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
        }

        if ($operation == 'DECODE') {
            if ((substr($result, 0, 10) == 0 || intval(substr($result, 0, 10)) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
                return substr($result, 26);
            } else {
                return '';
            }
        } else {
            return $keyc . str_replace('=', '', base64_encode($result));
        }
    }

    //XSS攻击
    static function remove_xss($val)
    {
        // remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed
        // this prevents some character re-spacing such as <java\0script>
        // note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some inputs
        $val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val);
        // straight replacements, the user should never need these since they're normal characters
        // this prevents like <IMG SRC=@avascript:alert('XSS')>
        $search = 'abcdefghijklmnopqrstuvwxyz';
        $search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $search .= '1234567890!@#$%^&*()';
        $search .= '~`";:?+/={}[]-_|\'\\';
        for ($i = 0; $i < strlen($search); $i++) {
            // ;? matches the ;, which is optional
            // 0{0,7} matches any padded zeros, which are optional and go up to 8 chars
            // @ @ search for the hex values
            $val = preg_replace('/(&#[xX]0{0,8}' . dechex(ord($search[$i])) . ';?)/i', $search[$i], $val); // with a ;
            // @ @ 0{0,7} matches '0' zero to seven times
            $val = preg_replace('/(�{0,8}' . ord($search[$i]) . ';?)/', $search[$i], $val); // with a ;
        }
        // now the only remaining whitespace attacks are \t, \n, and \r
        $ra1 = array(
            'javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed',
            'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base'
        );
        $ra2 = array(
            'onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut',
            'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate',
            'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut',
            'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend',
            'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange',
            'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete',
            'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover',
            'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange',
            'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted',
            'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload'
        );
        $ra = array_merge($ra1, $ra2);
        $found = true; // keep replacing as long as the previous round replaced something
        while ($found == true) {
            $val_before = $val;
            for ($i = 0; $i < sizeof($ra); $i++) {
                $pattern = '/';
                for ($j = 0; $j < strlen($ra[$i]); $j++) {
                    if ($j > 0) {
                        $pattern .= '(';
                        $pattern .= '(&#[xX]0{0,8}([9ab]);)';
                        $pattern .= '|';
                        $pattern .= '|(�{0,8}([9|10|13]);)';
                        $pattern .= ')*';
                    }
                    $pattern .= $ra[$i][$j];
                }
                $pattern .= '/i';
                $replacement = substr($ra[$i], 0, 2) . '<x>' . substr($ra[$i], 2); // add in <> to nerf the tag
                $val = preg_replace($pattern, $replacement, $val); // filter out the hex tags
                if ($val_before == $val) {
                    // no replacements were made, so exit the loop
                    $found = false;
                }
            }
        }
        return $val;
    }
}